﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace MiniIOC.IOCLIbs
{
    public class ServiceProvider : IServiceProvider, IDisposable
    {
        internal ConcurrentDictionary<Type, ServiceDescriptor> ServiceDescriptors { get; } = new ConcurrentDictionary<Type, ServiceDescriptor>();
        private readonly Dictionary<Type, object> _scopedCachedInstances;
        private readonly Collection<object> _transientValueCollection;
        private bool _disposed;

        public ServiceProvider()
        {
            _scopedCachedInstances = new Dictionary<Type, object>();
            _transientValueCollection = new Collection<object>();
        }

        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;

            var instances = _scopedCachedInstances.Values.ToList();
            _scopedCachedInstances.Clear();
            instances.AddRange(_transientValueCollection.ToList());
            _transientValueCollection.Clear();

            foreach (var instance in instances)
            {
                if (instance == null) continue;
                if (instance is IDisposable disposable) disposable.Dispose();
                else if (instance is IAsyncDisposable asyncDisposable) asyncDisposable.DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult();
            }
            instances.Clear();

            foreach (var serviceDescriptor in ServiceDescriptors)
            {
                var instance = serviceDescriptor.Value.ImplementationInstance;
                if (instance == null) continue;
                if (instance is IDisposable disposable) disposable.Dispose();
                else if (instance is IAsyncDisposable asyncDisposable) asyncDisposable.DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult();
            }
            ServiceDescriptors.Clear();
        }

        public object? GetService(Type serviceType)
        {
            if (_disposed == true) throw new InvalidOperationException("不能访问已释放的对象");

            var serviceDescriptor = GetServiceDescriptor(serviceType);
            if (serviceDescriptor != null)
            {
                switch (serviceDescriptor.Lifetime)
                {
                    case ServiceLifetime.Singleton:
                        if (serviceDescriptor.ImplementationInstance == null)
                        {
                            serviceDescriptor.ImplementationInstance = CreateInstance(serviceDescriptor.ImplementationType!);
                            return serviceDescriptor.ImplementationInstance;
                        }

                        return serviceDescriptor.ImplementationInstance;
                    case ServiceLifetime.Scoped:
                        {
                            if (_scopedCachedInstances.TryGetValue(serviceType, out object? value) && value != null) return value;

                            value = CreateInstance(serviceDescriptor.ImplementationType!);
                            _scopedCachedInstances.Add(serviceType, value!);

                            return value;
                        }
                    case ServiceLifetime.Transient:
                        {
                            var value = CreateInstance(serviceDescriptor.ImplementationType!);
                            _transientValueCollection.Add(value!);
                            return value;
                        }
                    default:
                        throw new NotSupportedException($"不支持的生命周期：{serviceDescriptor.Lifetime}");
                }
            }

            if (serviceType.IsValueType)
            {
                return Activator.CreateInstance(serviceType);
            }

            return null;
        }

        public T? GetService<T>()
        {
            if (_disposed == true) throw new InvalidOperationException("不能访问已释放的对象");
            return (T?)GetService(typeof(T));
        }

        private ServiceDescriptor? GetServiceDescriptor(Type serviceType)
        {
            var serviceProvider = this;
            ServiceDescriptor? serviceDescriptor = default;
            if (serviceType.IsGenericType == false)
            {
                serviceProvider?.ServiceDescriptors.TryGetValue(serviceType, out serviceDescriptor);
                return serviceDescriptor;
            }

            Type genericServiceType = serviceType;
            serviceType = genericServiceType.GetGenericTypeDefinition();
            if (serviceProvider?.ServiceDescriptors.TryGetValue(serviceType, out serviceDescriptor) != true)
                return serviceDescriptor;

            ServiceDescriptor? genericServiceDescriptor = default;
            if (serviceProvider?.ServiceDescriptors.TryGetValue(genericServiceType, out genericServiceDescriptor) == false)
            {
                lock (this)
                {
                    if (serviceProvider?.ServiceDescriptors.TryGetValue(genericServiceType, out genericServiceDescriptor) == false)
                    {
                        Type? genericImplementationType = serviceDescriptor?.ImplementationType?.GetGenericTypeDefinition().MakeGenericType(genericServiceType.GetGenericArguments());

                        genericServiceDescriptor = new ServiceDescriptor
                        {
                            ServiceType = genericServiceType,
                            ImplementationType = genericImplementationType,
                            Lifetime = serviceDescriptor!.Lifetime,
                        };

                        serviceProvider?.ServiceDescriptors.TryAdd(genericServiceType, genericServiceDescriptor);
                    }
                }
            }

            return genericServiceDescriptor;
        }

        private object? CreateInstance(Type implementationType)
        {
            #region 构造函数注入
            ConstructorInfo? constructorInfo = implementationType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).OrderByDescending(x => x.GetParameters()?.Length).FirstOrDefault();
            if (constructorInfo == null) throw new InvalidOperationException($"{implementationType.FullName} 必须拥有一个公共的构造函数");

            object? instance = default;
            ParameterInfo[]? parameterInfos = constructorInfo.GetParameters().OrderBy(p => p.Position).ToArray();
            if (parameterInfos?.Length > 0)
            {
                object?[] parameters = new object[parameterInfos.Length];
                for (int i = 0; i < parameterInfos.Length; i++)
                {
                    ParameterInfo parameterInfo = parameterInfos[i];
                    parameters[i] = GetService(parameterInfo.ParameterType);
                }

                instance = constructorInfo.Invoke(parameters);
            }
            else
            {
                instance = Activator.CreateInstance(implementationType);
            }
            #endregion

            #region 属性注入
            var properties = implementationType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanWrite && x.IsDefined(typeof(NonInjectAttribute), true) == false).ToArray();
            if (properties?.Length > 0)
            {
                for (int i = 0; i < properties.Length; i++)
                {
                    var property = properties[i];
                    object? propertyValue = GetService(property.PropertyType);
                    property.SetValue(instance, propertyValue);
                }
            }
            #endregion

            return instance;
        }
    }
}
